﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using UMC.Net;

namespace UMC.Host
{

    class HttpResponse
    {
        protected static readonly byte[] ChunkedEnd = Encoding.ASCII.GetBytes("0\r\n\r\n");
        public int StatusCode
        {
            get;
            set;
        }
        public StringValue ContentType
        {
            get;
            set;
        }
        public long? ContentLength { set; get; }
        HttpSocket _context;
        HttpRequest _req;
        // Action _headed;
        public HttpResponse(HttpSocket context, HttpRequest request)
        {
            this._req = request;
            this.StatusCode = 200;
            this._context = context;
            this.bodyStream = new BodyStream(this);
        }
        public NetLoger loger = new NetLoger(new ByteChunk());
        public Action Headed;


        List<HeadValue> headers = new List<HeadValue>();
        public void AddHeader(HeadValue head)
        {
            if (this.lengthWrite == 0)
            {
                headers.Add(head);//new HeadValue(name, value));// { Name = name, Value = value });
            // }
            // else
            // {
            //     loger.Debug().Write(_req.Url.AbsoluteUri).Write("内容已经输出不可再追加Header"u8).End();
                // UMC.Data.Utility.Debug("Http", DateTime.Now, "内容已经输出不可再追加Header", _req.Url.AbsoluteUri);
            }
        }
        public void AddHeader(string name, string value)
        {
            if (this.lengthWrite == 0)
            {
                headers.Add(new HeadValue(name, value));// { Name = name, Value = value });
            // }
            // else
            // {
            //     loger.Debug().Write(_req.Url.AbsoluteUri).Write("内容已经输出不可再追加Header"u8).End();
                // UMC.Data.Utility.Debug("Http", DateTime.Now, "内容已经输出不可再追加Header", _req.Url.AbsoluteUri);
            }
        }

        public void Redirect(StringValue url)
        {
            if (this.lengthWrite == 0)
            {
                this.StatusCode = 302;
                this.AddHeader(new HeadValue(StringValue.Location, url));
                this.ContentLength = null;
                this.isChunked = false;
                this.WriteHeader();
                this.lengthWrite = -1;
            // }
            // else
            // {
            //     loger.Debug().Write(_req.Url.AbsoluteUri).Write("内容已经输出不可再重定向"u8).End();
                // UMC.Data.Utility.Debug("Http", DateTime.Now, "内容已经输出不可再重定向", _req.Url.AbsoluteUri);
            }

        }

        BodyStream bodyStream;
        public System.IO.Stream OutputStream
        {
            get
            {
                return bodyStream;
            }
        }
        bool isChunked;
        void WriteHeader()
        {
            var sb = new UMC.Net.TextWriter(_context.Write);
            try
            {
                sb.Write("HTTP/1.1 {0} {1}\r\n", this.StatusCode, UMC.Net.HttpStatusDescription.Get(this.StatusCode));
                // sb.Flush();
                foreach (var h in this.headers)
                {
                    sb.Write(h.Name.Span);
                    sb.Write(": "u8);
                    sb.Write(h.Value.Span);
                    sb.Write("\r\n"u8);
                }
                if (this.ContentType?.IsEmpty == false)
                {
                    sb.Write("Content-Type: "u8);
                    sb.Write(this.ContentType.Span);
                    sb.Write("\r\n"u8);
                }

                if (this.ContentLength.HasValue && this.ContentLength > 0)
                {
                    sb.Write("Content-Length: "u8);
                    sb.Write(this.ContentLength);
                    sb.Write("\r\n"u8);
                }
                else if (isChunked)
                {
                    sb.Write("Transfer-Encoding: chunked\r\n"u8);
                }
                else
                {
                    sb.Write("Content-Length: 0\r\n"u8);
                }

                if (_IsClose || _req.IsHttpFormatError)
                {
                    sb.Write("Connection: close\r\n"u8);
                }
                else
                {
                    sb.Write("Keep-Alive: timeout=20\r\n"u8);
                    sb.Write("Connection: keep-alive\r\n"u8);
                }
                sb.Write("Server: APIUMC\r\n\r\n"u8);

                this.Headed?.Invoke();
            }
            finally
            {
                sb.Flush();
                sb.Dispose();
            }
        }
        bool _IsClose;
        public void OutputError(Exception ex)
        {
            if (lengthWrite == 0)
            {
                this.StatusCode = 500;
                _IsClose = true;

                this.ContentType = StringValue.ContentTypeText;

                var errStr = $"500 InternalServerError\r\n\r\n{ex.Message}";

                var by = Encoding.UTF8.GetBytes(errStr);

                loger.Error().Write(ex.ToString(), out var stringValue).End();
                this.ContentLength = by.Length + stringValue.Length;
                this.isChunked = false;

                bodyStream.Write(by, 0, by.Length);
                stringValue.Output(bodyStream.Write);

            }
            else if (this.isChunked)
            {
                _context.Write(ChunkedEnd, 0, ChunkedEnd.Length);
            }
        }
        public bool OutputFinish()
        {
            try
            {
                if (lengthWrite == -1)
                {
                    return true;
                }
                else if (lengthWrite == 0)
                {
                    if (this.ContentLength > 0)
                    {
                        _IsClose = true;

                        this.ContentLength = 0;
                        this.isChunked = false;
                        WriteHeader();
                        lengthWrite = -1;
                        return false;
                    }
                    else
                    {
                        this.ContentLength = 0;
                        this.isChunked = false;
                        WriteHeader();
                        lengthWrite = -1;
                        return true;
                    }

                }
                else if (this.isChunked)
                {

                    _context.Write(ChunkedEnd, 0, ChunkedEnd.Length);

                    return true;
                }
                else
                {
                    return lengthWrite == this.ContentLength;
                }
            }
            finally
            {
                headers.Clear();
                this.bodyStream.Dispose();
            }

        }
        long lengthWrite = 0;
        class BodyStream : System.IO.Stream
        {
            HttpResponse response;
            public BodyStream(HttpResponse response)
            {
                this.response = response;
            }
            public override bool CanRead => false;

            public override bool CanSeek => false;


            public override bool CanWrite => true;

            public override long Length => throw new NotImplementedException();

            public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

            public override void Flush()
            {
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                throw new NotImplementedException();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new NotImplementedException();
            }

            public override void SetLength(long value)
            {
                throw new NotImplementedException();
            }
            public override void Write(byte[] buffer, int offset, int count)
            {
                switch (response.lengthWrite)
                {
                    case -1:
                        // response.loger.Debug().Write(response._req.Url.AbsoluteUri).Write("内容输出已经关闭，不可再写入内容"u8).End();
                        return;
                    case 0:
                        response.isChunked = response.ContentLength.HasValue == false || response.ContentLength <= 0;
                        response.WriteHeader();
                        break;
                }
                response.lengthWrite += count;
                if (response.ContentLength > 0)
                {
                    if (response.ContentLength < response.lengthWrite)
                    {
                        var size = response.ContentLength.Value - (response.lengthWrite - count);
                        if (size > 0)
                        {
                            response._context.Write(buffer, offset, (int)size);
                        }
                        response._IsClose = true;
                    }
                    else
                    {
                        response._context.Write(buffer, offset, count);
                    }
                }
                else if (count > 0)
                {
                    var len = Encoding.ASCII.GetBytes($"{count:x}\r\n");

                    response._context.Write(len, 0, len.Length);
                    response._context.Write(buffer, offset, count);
                    response._context.Write(ChunkedEnd, 1, 2);
                }
            }
        }
    }

}